package vm.runtime;

import app.boot.LowMap;
import build.Bin.BinCommand;
import build.Bin.Var;
import expression.BoolExpression;
import expression.StringExpression;
import vm.baseapi.API;

import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class NextVMRuntime {
    public ConcurrentHashMap<String , Object> values = new ConcurrentHashMap<>();
    public String[] code = null;
    public File SourceFile = null;
    public NextVMRuntime LastRuntime = null;
    public ConcurrentHashMap<String,NextVMRuntime> Functions = new ConcurrentHashMap<>();
    public ArrayList<String> TypeList = new ArrayList<>();
    public ArrayList<BinCommand> buildCommand = new ArrayList<>();
    public boolean IsFunction = false;
    public LowMap FunctionInputValue = new LowMap();
    public String RuntimeName = null;
    public boolean IsPublicFunction = true;
    public String FunctionType = null;
    public int codeLine = 0;
    public Object ReturnContent = null;
    public boolean IFOK = true;
    public boolean IF_RUN = true;
    public ConcurrentHashMap<String ,Object> ClassMap = new ConcurrentHashMap<>();
    public boolean TRYOK = true;
    public boolean TRY_RUN = true;
    public String TRY_CODE = null;
    public String CATCH_MESSAGE = null;
    public boolean IsInLoop = false;
    public boolean AllowRun = true;
    public boolean IsGo = false;
    public boolean IsInTryCatch = false;
    public boolean IsInIfElse = false;
    public boolean IsInRootFunction = false;

    public NextVMRuntime(String RuntimeName) {
        String[] types = {
            "double",
            "string",
            "int",
            "float",
            "object",
            "thread",
            "long",
            "string[]",
            "bool",
            "int[]",
            "float[]",
            "byte[]",
            "double[]",
            "long[]",
            "output",
            "input",
            "_"
        };
        this.TypeList.addAll(Arrays.asList(types));
        this.RuntimeName = RuntimeName;
    }
    public void init_function(){

    }
    public static NextVMRuntime getFunctionFromRuntime(String name , NextVMRuntime runtime) {
        if (runtime.Functions.containsKey(name)) {
            return runtime.Functions.get(name);
        }
        else if (runtime.LastRuntime != null) {
            return getFunctionFromRuntime(name , runtime.LastRuntime);
        }
        else {
            return null;
        }
    }
    public boolean isFunction(String code) {
        try {
            String function = code.substring(0,code.indexOf("(")).trim();
            String content = code.substring(code.indexOf("(")+1 , code.lastIndexOf(")")).trim();
            return getFunctionFromRuntime(function , this) != null;
        }catch (Exception exception) {
            //exception.printStackTrace();
            return false;
        }
    }
    public String[] FunctionTokens(String code) throws Exception {
        String[] split = code.split(" ");
        String Type = split[1].trim();
        String Name = code.substring(
                BinCommand.findNthOccurrence(
                        code , " " , 2) , code.indexOf("(")).trim();
        String Input = code.substring(code.indexOf("(")+1 , code.lastIndexOf(")"));
        if (Input.isEmpty()) {
            ArrayList<String> arrayList = new ArrayList<>();

            arrayList.add(Type);
            arrayList.add(Name);
            arrayList.add(null);
            return arrayList.toArray(new String[0]);
        }
        String[] InputList = Input.split(",");
        ArrayList<String> releaseList = new ArrayList<>();
        for (int i = 0 ; i < InputList.length ;i++) {
            InputList[i] = InputList[i].trim();
            String[] TypeAndName = InputList[i].split(" ");
            releaseList.add(TypeAndName[0].trim());
            releaseList.add(TypeAndName[1].trim());
        }
        ArrayList<String> arrayList = new ArrayList<>();

        arrayList.add(Type);
        arrayList.add(Name);
        arrayList.addAll(releaseList);

        if (!code.replaceAll(" ","").endsWith("{")) {
            throw new Exception("Define a Function Error: Syntax Error.");
        }

        return arrayList.toArray(new String[0]);
    }

    public static String extractBraceContent(String code) {
        StringBuilder result = new StringBuilder();
        Stack<Integer> braceStack = new Stack<>();

        for (int i = 0; i < code.length(); i++) {
            char ch = code.charAt(i);

            if (ch == '{') {
                braceStack.push(i);
            } else if (ch == '}') {
                if (!braceStack.isEmpty()) {
                    int start = braceStack.pop();
                    String content = code.substring(start, i + 1);
                    result.append(content).append("\n");
                }
            }
        }

        return result.toString();
    }

    private void build_exec(String run_code , int i) throws Exception{
        //String regex = " (?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^\\(]*\\([^\\)]*\\))*[^\\)]*$)";
        String regex = " ";
        String[] tokens = null;
        if (run_code.isEmpty()) {
            return;
        }
        if (run_code.startsWith("#") || run_code.startsWith("{") || run_code.startsWith("}")) {
            return;
        }
        if (run_code.startsWith("return ") && this.IsFunction) {
            String InputValue = run_code.substring(7).trim();
            tokens = new String[]{"return" , InputValue};
            BinCommand binCommand = new BinCommand(this);
            binCommand.SourceCode = run_code;
            binCommand.putStructure(tokens);
            buildCommand.add(binCommand);
            return;
        }
        BinCommand binCommand = new BinCommand(this);
        if (isFunction(run_code) || API.IsNativeMethodCode(run_code)) {
            binCommand.IsFunction = true;
            tokens = this.SplitFunctionCodeTokens(run_code);
            //System.out.println(Arrays.toString(tokens));
        }
        else if (run_code.startsWith("var ")) {
            String Name = run_code.substring(4 , run_code.indexOf("=")).trim();
            String Value = run_code.substring(run_code.indexOf("=")+1).trim();

            if (Value.startsWith("fn::")) {
                String getFuncName = Value.substring(4).trim();
                this.values.put(Name , getFunctionFromRuntime(getFuncName, this));
                return;
            }
            else if (Value.startsWith("fn") && Value.endsWith("{")) {
                NextVMRuntime NewFunction = new NextVMRuntime(Name);
                NewFunction.IsFunction = true;
                NewFunction.SourceFile = this.SourceFile;
                NewFunction.LastRuntime = this;
                NewFunction.IsPublicFunction = false;

                String[] InputTokens = Value.substring(Value.indexOf("(")+1,Value.lastIndexOf(")")).split(",| ");
                if (InputTokens[0] != null) {
                    for (int j = 0 ; j < InputTokens.length ;j+=2) {
                        String Type = InputTokens[j];
                        String Names = InputTokens[j+1];
                        NewFunction.FunctionInputValue.put(Names , Type);
                    }
                }
                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                //System.out.println(functionTokens[1]+": "+FunctionCode);
                NewFunction.code = FunctionCode.toArray(new String[0]);
                NewFunction.FunctionType = "object";
                NewFunction.IsInRootFunction = true;
                NewFunction.build();
                this.Functions.put(Name , NewFunction);
                this.values.put(Name , NewFunction);
                FunctionCode.clear();
                FunctionCode = null;

                return;
            }


            binCommand.nextVMRuntime = this;
            binCommand.putStructure(new String[]{"auto_var" , Name , Value});
            buildCommand.add(binCommand);
            return;
        }
        else {
            /**
             * 当检测到是正在创建函数的时候执行以下方法
             */
            if (run_code.startsWith("fn ")) {
                String[] functionTokens = FunctionTokens(run_code);
                NextVMRuntime NewFunction = new NextVMRuntime(functionTokens[1].trim());
                NewFunction.IsFunction = true;
                NewFunction.SourceFile = this.SourceFile;
                NewFunction.LastRuntime = this;
                NewFunction.IsPublicFunction = functionTokens[1].startsWith("_");

                String[] InputTokens = Var.joinStringsIndexOf(functionTokens, 2);
                if (InputTokens[0] != null) {
                    for (int j = 0 ; j < InputTokens.length ;j+=2) {
                        String Type = InputTokens[j];
                        String Name = InputTokens[j+1];
                        NewFunction.FunctionInputValue.put(Name , Type);
                    }
                }
                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                //System.out.println(functionTokens[1]+": "+FunctionCode);
                NewFunction.code = FunctionCode.toArray(new String[0]);
                NewFunction.FunctionType = functionTokens[0];
                NewFunction.IsInRootFunction = true;
                NewFunction.build();
                this.Functions.put(functionTokens[1].trim() , NewFunction);
                return;
            }
            /**
             * 当检测到是调用for关键字的时候执行
             */
            else if (run_code.startsWith("for ")) {
                if (!run_code.endsWith("{")) {
                    throw new Exception("Build Error: No '{' in the end.");
                }
                String range = run_code.substring(4,run_code.lastIndexOf("{"));

                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                ArrayList<String> NewTokens = new ArrayList<>();
                NewTokens.add("for");
                NewTokens.add(range);
                NewTokens.addAll(FunctionCode);
                tokens = NewTokens.toArray(new String[0]);
            }
            else if (run_code.startsWith("while ")) {
                if (!run_code.endsWith("{")) {
                    throw new Exception("Build Error: No '{' in the end.");
                }
                boolean whileOK = BoolExpression.getBool( run_code.substring(6,run_code.lastIndexOf("{")) , this);

                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                ArrayList<String> NewTokens = new ArrayList<>();
                NewTokens.add("while");
                NewTokens.add(String.valueOf(whileOK));
                NewTokens.addAll(FunctionCode);
                tokens = NewTokens.toArray(new String[0]);
            }
            else if (run_code.startsWith("class ")) {
                if (!run_code.endsWith("{")) {
                    throw new Exception("Build Error: No '{' in the end.");
                }
                String ClassName = run_code.substring(6,run_code.lastIndexOf("{")).trim();

                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                ArrayList<String> NewTokens = new ArrayList<>();
                NewTokens.add("class");
                NewTokens.add(ClassName);
                NewTokens.addAll(FunctionCode);
                tokens = NewTokens.toArray(new String[0]);
            }
            /**
             * 操作 if - else 语句
             */
            else if (run_code.startsWith("if ")) {
                if (!run_code.endsWith("{")) {
                    throw new Exception("Build Error: No '{' in the end.");
                }
                String whileOK = run_code.substring(3,run_code.lastIndexOf("{"));

                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                ArrayList<String> NewTokens = new ArrayList<>();
                NewTokens.add("if");
                NewTokens.add(whileOK);
                NewTokens.addAll(FunctionCode);
                this.IFOK = false;
                tokens = NewTokens.toArray(new String[0]);
            }
            else if (run_code.replaceAll(" ","").startsWith("else{")) {
                if (!IFOK) {

                    int FormatNumber = 0;
                    ArrayList<String> FunctionCode = new ArrayList<>();
                    for (int j = this.codeLine ; j < code.length;j++) {
                        String FormatCode = code[j].trim();
                        if (FormatCode.endsWith("{")) {
                            FormatNumber = FormatNumber + 1;
                            //System.out.println(FormatCode+" "+FormatNumber);
                            FunctionCode.add(FormatCode);
                            continue;
                        }
                        else if (FormatCode.endsWith("}")) {
                            FormatNumber -= 1;
                            //System.out.println(FormatCode+" "+FormatNumber);
                            FunctionCode.add(FormatCode);
                            continue;
                        }
                        if (FormatNumber == 0) {
                            break;
                        }
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                    }
                    this.codeLine += FunctionCode.size()-1;
                    FunctionCode.remove(0);
                    FunctionCode.remove(FunctionCode.size()-1);
                    ArrayList<String> NewTokens = new ArrayList<>();
                    NewTokens.add("else");
                    NewTokens.addAll(FunctionCode);
                    this.IFOK = false;
                    tokens = NewTokens.toArray(new String[0]);
                }
            }
            else if (run_code.replaceAll(" ","").startsWith("try{")) {
                if (TRYOK) {

                    int FormatNumber = 0;
                    ArrayList<String> FunctionCode = new ArrayList<>();
                    for (int j = this.codeLine ; j < code.length;j++) {
                        String FormatCode = code[j].trim();
                        if (FormatCode.endsWith("{")) {
                            FormatNumber = FormatNumber + 1;
                            //System.out.println(FormatCode+" "+FormatNumber);
                            FunctionCode.add(FormatCode);
                            continue;
                        }
                        else if (FormatCode.endsWith("}")) {
                            FormatNumber -= 1;
                            //System.out.println(FormatCode+" "+FormatNumber);
                            FunctionCode.add(FormatCode);
                            if (FormatNumber == 0) {
                                break;
                            }
                            continue;
                        }
                        if (FormatNumber == 0) {
                            break;
                        }
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                    }
                    this.codeLine += FunctionCode.size()-1;
                    FunctionCode.remove(0);
                    FunctionCode.remove(FunctionCode.size()-1);
                    ArrayList<String> NewTokens = new ArrayList<>();
                    NewTokens.add("try");
                    NewTokens.addAll(FunctionCode);
                    this.TRYOK = false;
                    tokens = NewTokens.toArray(new String[0]);
                }
                else {
                    throw new Exception("Build Error: Syntax error.");
                }
            }
            else if (run_code.startsWith("catch ")) {
                String error_code = run_code.substring(6,run_code.lastIndexOf("{")).trim();
                int FormatNumber = 0;
                ArrayList<String> FunctionCode = new ArrayList<>();
                for (int j = this.codeLine ; j < code.length;j++) {
                    String FormatCode = code[j].trim();
                    if (FormatCode.endsWith("{")) {
                        FormatNumber = FormatNumber + 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        continue;
                    }
                    else if (FormatCode.endsWith("}")) {
                        FormatNumber -= 1;
                        //System.out.println(FormatCode+" "+FormatNumber);
                        FunctionCode.add(FormatCode);
                        if (FormatNumber == 0) {
                            break;
                        }
                        continue;
                    }
                    if (FormatNumber == 0) {
                        break;
                    }
                    //System.out.println(FormatCode+" "+FormatNumber);
                    FunctionCode.add(FormatCode);
                }
                this.codeLine += FunctionCode.size()-1;
                FunctionCode.remove(0);
                FunctionCode.remove(FunctionCode.size()-1);
                ArrayList<String> NewTokens = new ArrayList<>();
                NewTokens.add("catch");
                NewTokens.add(error_code);
                NewTokens.addAll(FunctionCode);
                this.TRYOK = true;
                tokens = NewTokens.toArray(new String[0]);
            }
            else {
                tokens = run_code.split(regex);
            }
        }
        String[] RunClassFunctionTokens = GetTokenClassFunction(run_code);
        if (RunClassFunctionTokens != null) {
            tokens = RunClassFunctionTokens;
        }
        else if (CheckIsFunction(run_code)){
            binCommand.IsFunction = true;
            //String ContentRegex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^\\(]*\\([^\\)]*\\))*[^\\)]*$)";
            String Name = run_code.substring(0,run_code.indexOf("(")).trim();
            String[] InputContent = StringExpression.splitStringTokens(run_code.substring(run_code.indexOf("(")+1,
                    run_code.lastIndexOf(")")).trim());
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add(Name);
            arrayList.addAll(Arrays.asList(InputContent));
            tokens = arrayList.toArray(new String[0]);
        }
        binCommand.SourceCode = run_code;
        binCommand.putStructure(tokens);
        //System.out.println(Arrays.toString(tokens));
        buildCommand.add(binCommand);
    }

    public void build() throws Exception {

        for (this.codeLine = 0; this.codeLine < this.code.length ;codeLine++) {
            //System.out.println(this.codeLine+" "+this.code.get(this.codeLine).trim());
            build_exec(this.code[(this.codeLine)].trim() , this.codeLine);
        }
    }
    public void run() throws Exception{
        for (BinCommand binCommand : this.buildCommand) {
            if (!AllowRun) {
                break;
            }
            if (IsGo) {
                continue;
            }
            binCommand.VMRun();
        }
    }
    public String[] GetTokenClassFunction(String run_code) {
        try
        {
            String ClassValue = run_code.substring(0,run_code.indexOf(".")).trim();
            String Invoke = run_code.substring(run_code.indexOf(".")+1,run_code.indexOf("(")).trim();
            String Content = run_code.substring(run_code.indexOf("(")+1,run_code.lastIndexOf(")")).trim();
            //String ContentRegex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^\\(]*\\([^\\)]*\\))*[^\\)]*$)";
            if (ClassValue.contains(" ")) {
                return null;
            }
            String[] InputContent = StringExpression.splitStringTokens(Content);
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add("class_func");
            arrayList.add(ClassValue);
            arrayList.add(Invoke);
            arrayList.addAll(Arrays.asList(InputContent));
            return arrayList.toArray(new String[0]);
        }
        catch (Exception e) {
            return null;
        }
    }
    public String[] SplitFunctionCodeTokens(String code) throws Exception {
        ArrayList<String> splitArray = new ArrayList<>();
        String function = code.substring(0,code.indexOf("(")).trim();
        String content = code.substring(code.indexOf("(")+1 , code.lastIndexOf(")")).trim();
        splitArray.add(function);

        //String regex = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)(?=([^(]*\\([^)]*\\))*[^)]*$)";
        String[] splitContent = StringExpression.splitStringTokens(content);
        splitArray.addAll(Arrays.asList(splitContent));

        return splitArray.toArray(new String[0]);
    }
    public static boolean CheckIsFunction(String expression) {
        try {
            String funcName = expression.substring(0,expression.indexOf("(")).trim();
            String Content = expression.substring(expression.indexOf("(")+1,expression.lastIndexOf(")"));
            return !funcName.contains(" ");
        }catch (Exception e) {
            return false;
        }
    }
    public static NextVMRuntime isRunFunction(String expression , NextVMRuntime nextVMRuntime) {
        try {
            /**
             * [注释补充]
             * 判断是否是可调用的 函数
             *
             */
            //System.out.println(1);
            String funcName = expression.substring(0,expression.indexOf("(")).trim();
            return getFunctionFromRuntime(funcName , nextVMRuntime);
        }catch (Exception exception) {
            return null;
        }
    }
}
